位运算
在Review代码时候,看到一段涉及到USB的逻辑代码,他是这样写的
private boolean isUsbConnected;
private boolean isUsbModeNCM;
private boolean isUsbModeAccessory;
private boolean isUsbModeAdb;
private boolean isUsbModeMTP;
...
然后代码逻辑里是大量的成员变量的判断,显得非常臃肿而且难读懂,大量的if-else判断让代码逻辑很脆弱,稍微一个情况没考虑好就会出现难以排查的bug。
所以这种情况使用位掩码进行处理会更简单:
// 博客地址:wossoneri.github.io
private static final int FLAG_USB_CONNECTED = 0x1;
private static final int FLAG_USB_MODE_NCM = 0x1 << 1;
private static final int FLAG_USB_MODE_ACY = 0x1 << 2;
private static final int FLAG_USB_MODE_ADB = 0x1 << 3;
private static final int FLAG_USB_MODE_MTP = 0x1 << 4;
...
private int mUsbState;
public void addUsbState(int flag) {
mUsbState |= flag;
}
public void removeUsbState(int flag) {
mUsbState &= ~flag;
}
public boolean isUsbStateEnable(int flag) {
return (mUsbState & flag) == flag;
}
简单分析一下这样写的好处:
FLAG_USB_CONNECTED = 0001
FLAG_USB_MODE_NCM = 0010
FLAG_USB_MODE_ACY = 0100
FLAG_USB_MODE_ADB = 1000
通过移位,使得每一位都有独立的代表的意义,1代表enable,0代表disable。
如果要添加状态(Java里int值默认赋值为0):
public void addUsbState(int flag) {
mUsbState |= flag;
}
假设添加accessory状态FLAG_USB_MODE_ACY
0000 |= 0100 -> 0100
所以mUsbState就是0100的状态了。
继续添加FLAG_USB_MODE_ADB状态
0100 |= 1000 -> 1100
也可以一次添加多个状态,比如上面的两个状态在一次设置同时添加:
addUsbState(FLAG_USB_MODE_ACY | FLAG_USB_MODE_ADB);
结果就是:
0000 |= (0100 | 1000)
-> 0000 |= 1100
-> 1100
如果是原来的boolean变量,就需要单独为每一个变量设置,就会很麻烦。
然后是移除状态:
public void removeUsbState(int flag) {
mUsbState &= ~flag;
}
比如接着上面移除FLAG_USB_MODE_ADB状态
1100 &= ~1000
-> 1100 &= 0111
-> 0100
如果移除一个不存在的状态比如FLAG_USB_MODE_NCM
0100 &= ~0010
-> 0100 &= 1101
-> 0100
可以看到并不会对当前状态造成任何影响。
最后看一下检查状态:
public boolean isUsbStateEnable(int flag) {
return (mUsbState & flag) == flag;
}
首先检查一下当前拥有的状态:
(0100 & 0100) == 0100
-> 0100 == 0100
-> true
可以检测到该状态。然后换一个状态:
(0100 & 1000) == 1000
-> 0000 == 1000
-> false
没有检测到该状态。
所以,通过三个简单的方法,就可以检查一个变量里保存的所有状态,避免了使用大量bool变量进行挨个检查。简化了代码,增加代码可读性,并且使代码更加稳定。
进阶!使用EnumSet替代位运算
到这里你可能觉得问题解决了就完了,但是还没有!
实际上,《Effective Java》这本书有对位域的一项讨论:
位域表示法也允许利用位操作,有效的执行像union和intersection这样的集合操作。但位域有着int枚举常量所有的缺点,甚至更多。当位域以数字形式打印时,翻译位域比翻译简单的int枚举常量要困难很多。甚至要遍历位域表示的所有元素也没有很容易的方法。
Java.util包提供了EnumSet类来有效地表示从单个枚举类型中提取的多个值的多个集合。这个类实现Set接口,提供丰富的功能、类型安全性以及可从其他Set实现中得到的互用性。
内部实现上,每个EnumSet内容都表示为位矢量,一般(低于64个元素)整个EnumSet就是用一个long的位运算来表示的。也就是说它替你使用位算法实现了这一切,避免你自己写位运算导致代码难读懂的情况。
下面是用EnumSet修改后的示例代码,它更加简短,清楚也更安全。
// 博客地址:wossoneri.github.io
public class UsbManager {
private EnumSet mUsbState = EnumSet.noneOf(UsbFlags.class);
public enum UsbFlags {
CONNECTED, NCM, ACCESSORY, ADB, MTP
}
public void addFlag(UsbFlags flag) {
mUsbState.add(flag);
System.out.println("After add flag " + flag + ", Now state is " + this.mUsbState);
}
public void addFlag(Set flags) {
mUsbState.addAll(flags);
System.out.println("After add flags " + flags + ", Now state is " + this.mUsbState);
}
public void removeFlag(UsbFlags flag) {
mUsbState.remove(flag);
System.out.println("After remove flag " + flag + ", Now state is " + this.mUsbState);
}
public void removeFlag(Set flags) {
mUsbState.removeAll(flags);
System.out.println("After remove flags " + flags + ", Now state is " + this.mUsbState);
}
public boolean checkFlagEnabled(UsbFlags flag) {
return mUsbState.contains(flag);
}
public boolean checkFlagEnabled(Set flag) {
return mUsbState.containsAll(flag);
}
public void printUsbState() {
System.out.println("Current usb state is " + mUsbState);
}
}
测试用例以及输出
public static void main(String[] args) {
// 博客地址:wossoneri.github.io
UsbManager usbManager = new UsbManager();
usbManager.printUsbState();
// 添加一项flag
usbManager.addFlag(UsbFlags.CONNECTED);
// 添加一组 flag
usbManager.addFlag(EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.ADB));
// 检查存在的一个flag
System.out.println("mUsbState contains flag " + UsbFlags.ACCESSORY + ": " +
usbManager.checkFlagEnabled(UsbFlags.ACCESSORY));
// 检查不存在的一个flag
System.out.println("mUsbState contains flag " + UsbFlags.MTP + ": " +
usbManager.checkFlagEnabled(UsbFlags.MTP));
// 检查一组存在的flag
System.out.println("mUsbState contains flag " + EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.ADB) + ": " +
usbManager.checkFlagEnabled(EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.ADB)));
// 检查一组包含不存在的flag
System.out.println("mUsbState contains flag " + EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.MTP) + ": " +
usbManager.checkFlagEnabled(EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.MTP)));
// 检查一组都不存在的flag
System.out.println("mUsbState contains flag " + EnumSet.of(UsbFlags.NCM, UsbFlags.MTP) + ": " +
usbManager.checkFlagEnabled(EnumSet.of(UsbFlags.NCM, UsbFlags.MTP)));
usbManager.printUsbState();
// 删除一个不存在的flag
usbManager.removeFlag(UsbFlags.MTP);
// 删除一个存在的flag
usbManager.removeFlag(UsbFlags.ACCESSORY);
// 删除一组都不存在的flag
usbManager.removeFlag(EnumSet.of(UsbFlags.NCM, UsbFlags.MTP));
// 删除一组包含不存在的flag
usbManager.removeFlag(EnumSet.of(UsbFlags.NCM, UsbFlags.ADB));
usbManager.addFlag(EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.ADB));
// 删除一组存在的flag
usbManager.removeFlag(EnumSet.of(UsbFlags.ADB, UsbFlags.ACCESSORY));
}
输出为
Current usb state is []
After add flag CONNECTED, Now state is [CONNECTED]
After add flags [ACCESSORY, ADB], Now state is [CONNECTED, ACCESSORY, ADB]
mUsbState contains flag ACCESSORY: true
mUsbState contains flag MTP: false
mUsbState contains flag [ACCESSORY, ADB]: true
mUsbState contains flag [ACCESSORY, MTP]: false
mUsbState contains flag [NCM, MTP]: false
Current usb state is [CONNECTED, ACCESSORY, ADB]
After remove flag MTP, Now state is [CONNECTED, ACCESSORY, ADB]
After remove flag ACCESSORY, Now state is [CONNECTED, ADB]
After remove flags [NCM, MTP], Now state is [CONNECTED, ADB]
After remove flags [NCM, ADB], Now state is [CONNECTED]
After add flags [ACCESSORY, ADB], Now state is [CONNECTED, ACCESSORY, ADB]
After remove flags [ACCESSORY, ADB], Now state is [CONNECTED]
综上,代码唯一要注意的是
public boolean checkFlagEnabled(Set flag)
传入参数使用了Set接口,这是考虑到可能会传入其他Set的实现类型,所以传入接口参数要好于实现类型参数。
最后,EnumSet类集成了位域自身的简洁性和性能优势,又拥有枚举的所有优点,所以使用它代替位域是非常好的选择。